package capimServer;

import java.net.*;

/**
 * Class to reveice UDP Datagrams from network
 * @author danielm pessoafpc
 *
 */
public class ReceivePackets implements Runnable{
	protected Thread runner;
	protected LoginClass login;
	private int localport,expectedPackage;
	// Control free ports 
	private PortControl ports;
	
	/**
	 * Default Constructor
	 */
	public ReceivePackets(){
		this.setExpectedPackage(1);
		this.setLocalport(CapimServer.port);
		login = new LoginClass();
		ports = new PortControl();
		if(runner == null){
			runner = new Thread(this);
			runner.start();
		}
	}
	
	/**
	 * Constructor used when the local port changes
	 * @param port port to listen UDP datagrams
	 */
	public ReceivePackets(int port){
		login = new LoginClass();
		this.setExpectedPackage(1);
		this.setLocalport(port);
		if(runner == null){
			runner = new Thread(this);
			runner.start();
		}
	}

	/**
	 * Thread main job
	 */
	@Override
	public void run(){
               try {
            	   	  // Create the socket on specified port
                      DatagramSocket serverSocket = new DatagramSocket(this.getLocalport());
                      
                      while (true) {
                    	  // Declare a byte vector to write the data in
                    	  byte [] receiveData = new byte[1024];
                    	  // Declare a datagram to put the received data
                    	  DatagramPacket receivePacket = new DatagramPacket(receiveData,receiveData.length);
                    	  
                    	  // Receive the datagram
                    	  serverSocket.receive(receivePacket);
                    	  // Create a new string to receive data from datagram
                    	  String rdata = new String(receivePacket.getData());
                    	  // Show the data received on default output
                    	  System.out.println("Received packet: "+rdata);
                    	  // Check if the packet is or not an ACK Packet
                    	  if(rdata.charAt(0) == '2'){
	          					System.out.println("Received an ack: "+rdata);
	          					// Send the ACK to the sender object
	          					CapimServer.postOffice.analizeReceivedPacket(rdata);
	          				} else {
		          				// Send an ACK
	          					// Construct a string to receive package sequence number
	        					StringBuilder numPackRecv = new StringBuilder("");
	        					for(int i=1; i< rdata.length() ; i++){
	        						if(rdata.charAt(i)==':') break;
	        						numPackRecv.append(rdata.charAt(i));
	        					} 
	        					String packageSequenceNumber = new String(numPackRecv.toString().trim());
	        					
	        					// Send an ACK to sinalize the last correct packet received
	        					if((Integer.parseInt(packageSequenceNumber)) == this.getExpectedPackage()){
	        						CapimServer.postOffice.sendAck(receivePacket.getAddress(),"2"+this.getExpectedPackage());
	        						this.setExpectedPackage(this.getExpectedPackage()+1);
	        					} else CapimServer.postOffice.sendAck(receivePacket.getAddress(),"2"+this.getExpectedPackage());
	        					
	        					// Unpack the message received
	        					this.unpackMessage(receivePacket);
	          				}
                    	  
                    	  // Just for debug
                    	  // System.out.println("Pacote recebido: "+new String(receivePacket.getData()));
                      }
                      
               }
               catch (Exception err){
                  System.err.println(err);
               }
        }
        
	
	
        /**
         * Class to extract message type and call the correct method to ?thrate? the message
         * @param packet packet received through udp connection
         * @throws Exception 
         */
        public void unpackMessage(DatagramPacket packet) throws Exception{
        	String data = new String(packet.getData());
        	
        	// Just for debug
        	// System.out.println("Dados do pacote: "+data);
        	// System.out.println("charAt(0)"+data.charAt(0)+"charAt(2): "+data.charAt(2));
    		// IF CHANGE HEADER, MUST CHANGE HERE
        	
        	// Search for the end of the packet sequence number
        	char option = 0;
    		int index = 0;
        	for(int i=1; i< data.length() ; i++){
        		if(data.charAt(i) == ':'){
        			index=i+1;
        			break;
        		}
        	}
        	option = data.charAt(index);
        	switch(option){
        		case '1': // Login message
        			// Cut header
        			char[] nData = new char[data.length()-index];
        			data.getChars(index, data.length(), nData, 0);
        			String tmp = new String(nData);
        			// check login
        			login.checkMessage(tmp,packet.getAddress());
        			break;
        			
        		case '2':
        			// TODO not yet implemented
        			System.out.println("List Retrieve received");
        			break;
        			
        		case '3':
        			// TODO not yet implemented
        			break;
        			
        		case '4':
        			// TODO not yet implemented
        			break;
        			
        		case '5':
        			// TODO not yet implemented
        			break;
        			
        		case '6':
        			this.connection(packet);
        			break;
        			
        		default:
        			break;
        	}
        }
        
        
        /**
         * Negociate the ports to continue comunication
         * @param packet
         * @throws Exception
         */
        private final synchronized void connection(DatagramPacket packet) throws Exception{
        	String cData = new String(packet.getData());
        	// Check if is a new conection request
        	switch(cData.charAt(3)){
	        	case '1':
	        		// Take a new port to continue comunication
	        		int newport = ports.getPort();
	        		// Send the new port to the other side
	        		CapimServer.postOffice.sendMessage(packet.getAddress(), new String("162"+newport));
	        		// Start a new thread to continue this conection
	        		ReceivePacketsForConnection rcv = new ReceivePacketsForConnection(newport);
	        		break;
	        		
	    		default:
	    			break;
	        	
        	}
        	
        }

		public void setLocalport(int localport) {
			this.localport = localport;
		}

		public int getLocalport() {
			return localport;
		}
		
		public int getExpectedPackage() {
			return expectedPackage;
		}

		public void setExpectedPackage(int expectedPackage) {
			this.expectedPackage = expectedPackage;
		}

}
